package dkbta.server.domain.cu;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.logging.Level;

import org.apache.log4j.Logger;

import dkbta.Config;
import dkbta.server.gui.Observable;
import dkbta.server.gui.Observer;
import dkbta.server.util.query.QueriesTable;
import dkbta.server.util.query.Query;
import dkbta.util.Pair;
import static dkbta.util.Utils.now;


/**
 * @author Arkady Mishiev
 *
 */

public class CUStub extends Thread implements Observable{

	private static Logger s_logger;
	private static int s_idCount = 0;	
	private String m_id;
	private Socket m_cuSocket;	
	private PrintWriter m_out;
	private BufferedReader m_in;
	private Observer m_logsPanelObserver;		
	private QueriesTable m_queriesTable;
	private String m_currResults;
	private String m_leftOver;
	/*<queryId,plan>*/
	private volatile Pair<String, String> m_currentPlan;
	private volatile boolean m_shouldRun;
	private volatile boolean m_havePlan;
	private volatile boolean m_isAvailable;
        private Query m_currQuery;
        private Observer m_queriesPanelObserver;
        private String m_startTime;
        private Observer m_unitsPanelObserver;
        private String m_finishTime;
        private boolean m_isFinished;

	public CUStub(Socket clientSocket, QueriesTable queriesTable) {
		super("CUStub");
		try {
			s_logger = Logger.getLogger(CUStub.class);
			m_havePlan = false;
			m_queriesTable = queriesTable;
			m_cuSocket = clientSocket;
			m_leftOver = "";
                        m_isFinished = false;
			setUpId();
			m_out = new PrintWriter(m_cuSocket.getOutputStream(), true);
			m_in = new BufferedReader(new InputStreamReader(m_cuSocket.getInputStream()));
			/*send id*/
	 		m_out.print("" + m_id.substring(0, m_id.indexOf("@")) + Config.FRAME);
			m_out.flush();
			s_logger.info("Initialized ok. id " + m_id + "");		
			m_shouldRun = true;
                        m_startTime = now("H:mm:ss");
                        m_finishTime = "N/A";
			setAvailableState(true);				
		} catch (IOException e) {
			m_shouldRun = false;
			s_logger.warn("Failed to init. Connection failure");
		}
	}

	public void run(){
		m_logsPanelObserver.updateLogsPanel("CUStub started. id " + m_id + ".");
                m_unitsPanelObserver.updateUnitsPanel(this);
		while(m_shouldRun){
			/*state - is Ready*/
			waitForPlan();
			/*state - not Ready*/
			sendPlan();
			waitForResults();
			/*state - is Ready*/
			sendResults();
		}
		s_logger.warn("Finished workiing. id " + m_id + ".");
		m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + " finished");
		reportFailure();
                m_isFinished = true;
                m_finishTime = now("H:mm:ss");
                m_unitsPanelObserver.updateUnitsPanel(this);
	}


	private void reportFailure() {
		s_logger.warn("Begin reporting failure. id " + m_id + ".");
		m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + 
				" Reporting failure");
		if(m_currentPlan != null){
			/*queryId,stubId*/
			m_queriesTable.reportUnitsFailure(m_currentPlan.getFirst(), m_id);
		}
		else{
			s_logger.warn("Nothing to report. id " + m_id + ".");
		}















                s_logger.warn("End reporting failure. id " + m_id + ".");
	}

	private void sendResults() {
		try{
			if(m_currResults.startsWith(Config.RESULTS_PREFIX)){
				m_currResults = m_currResults.substring(Config.RESULTS_PREFIX.length());
				m_queriesTable.addResults(m_currentPlan.getFirst(), m_currResults, m_id);
				s_logger.info("Results received");
				return;
			}
			if(m_currResults.startsWith(Config.FAILURE_PREFIX)){			
				m_currResults = m_currResults.substring(Config.FAILURE_PREFIX.length());
				s_logger.warn("Unsupported message type from CU. type - failure");
				m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + 
						" Unsupported message type from CU. type - failure");
				return;
			}
			s_logger.warn("UNKNOWN MESSAGE TYPE!!! msg :" + m_currResults + ".");
			m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + " UNKNOWN MESSAGE TYPE!!!");
		}
		catch(Exception e){
			if(m_shouldRun){
				s_logger.warn("Unknown exception while sending results!!!");
				m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + 
						" Unknown exception while sending results!!!");
				e.printStackTrace();
			}
			else{
				s_logger.warn("The stub should be removed. Connection failure");
			}
		}
	}

	private void waitForResults() {
		s_logger.info("Begin waiting for results from unit");
		m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + " waitig for results");
		m_currResults = receiveMsg();
		setAvailableState(true);
                m_currQuery = null;                
                m_unitsPanelObserver.updateUnitsPanel(this);
		m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + " received results");
	}

	public String receiveMsg() {
		String ans = m_leftOver;
		String toReturn = null;
		boolean readMore = true;
		final char[] charBuffer = new char[10000];
		final StringBuilder sb = new StringBuilder();
		int k = 0;
		while(readMore){			
			try {
				k = m_in.read(charBuffer);
			} catch (IOException e) {
				s_logger.warn("The connection with unit failed!!!");
				m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + " lost connection");
				closeStub();
				break;
			}			
			if(k == -1){
				m_shouldRun = false;				
				s_logger.warn("Failure in inputStream with unit!!!");
				m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + " lost connection");
				closeStub();
				break;
			}
			sb.append(charBuffer,0,k);
			ans = ans + sb.toString();
			if(ans.contains(Config.FRAME)){
				readMore = false;
				String left = ans.substring(ans.indexOf(Config.FRAME) + 
						Config.FRAME.length(),ans.length());
				m_leftOver = left;
				ans = ans.substring(0,ans.indexOf(Config.FRAME));
				toReturn = ans.replace(Config.FRAME,"");
			}        
		}
		return toReturn ;
	}	

	private void sendPlan() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException ex) {
            
        }
		m_out.print("plan#" + "qid:" + m_currentPlan.getFirst() + 
				"#" + m_currentPlan.getSecond() + Config.FRAME);
		m_out.flush();
		s_logger.info("Plan sent to unit");
                m_currQuery.setState(Query.PlanState.PROCESSING);
                m_queriesPanelObserver.updatePlansPanel(m_currQuery);
		m_logsPanelObserver.updateLogsPanel("Plan sent to unit");
	}

	private synchronized void waitForPlan() {
		s_logger.info("Ready to get a plan");
		m_logsPanelObserver.updateLogsPanel(m_id + " Ready to get a plan");
		while(!m_havePlan){
			try {
				wait();
			} catch (InterruptedException e) {
				s_logger.warn("Interrupted while waiting to plan!!!");
				m_logsPanelObserver.updateLogsPanel("Interrupted while waiting to plan!!!");
			}
		}
		s_logger.info(m_id + " Received plan");
		m_logsPanelObserver.updateLogsPanel(m_id + " Received plan");
                m_unitsPanelObserver.updateUnitsPanel(this);
		m_havePlan = false;		
	}

	public synchronized void addPlan(Pair<String, String> stubsPlan, Query query){
                m_currQuery = query;
		m_currentPlan = stubsPlan;
		m_havePlan = true;
		setAvailableState(false);                
		s_logger.info("Plan added to CUStubs inbox");
		m_logsPanelObserver.updateLogsPanel("Plan added to CUStubs inbox");
		notifyAll();		
	}

	private synchronized void setAvailableState(boolean b) {
		m_isAvailable = b;
	}

	public synchronized boolean isAvailable() {
		boolean ans = m_isAvailable & m_shouldRun;
		//s_logger.debug("The state is :" + ans);
		return ans;
	}

	private void setUpId() {
		m_id = String.valueOf(++s_idCount) + "@";
		m_id = m_id + m_cuSocket.getInetAddress().getHostAddress();
		m_id = m_id + ":" + m_cuSocket.getPort();		
	}

	public String getCUId() {
		return m_id;
	}

	private void closeStub() {
		try {
			m_shouldRun = false;
			setAvailableState(false);
                        m_currQuery = null;
                        //m_currentPlan = null;
			m_out.close();
			m_in.close();
			m_cuSocket.close();
			s_logger.info("id :" + m_id + " closed ok");
			m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + " closed");
		} catch (IOException e) {
			s_logger.warn("Failed to close resources");
			m_logsPanelObserver.updateLogsPanel("CUStub " + m_id + " failed while closing");
		}				
	}

	@Override
	public void addCUPanelObserver(Observer o) {
            m_unitsPanelObserver = o;
	}

	@Override
	public void addLogsPanelObserver(Observer o) {
		m_logsPanelObserver = o;		
	}

	@Override
	public void addQueriesPanelObserver(Observer o) {
            m_queriesPanelObserver = o;
	}

    public String getStartTime() {
        return m_startTime;
    }

    public String getFinishTime(){
        return m_finishTime;
    }

    public String getUnitState() {
        if(m_isFinished){
            return "DISCONNECTED";
        }
        else{
            if(isAvailable())
                return "READY";
            else
                return "BUSY";
        }
    }

    public String getPlanId() {
        if(m_currQuery != null){
            return m_currQuery.getId();
        }
        else{
            return "N/A";
        }
    }

}

